How can I decode JWT (JSON web token) token in Swift?
Asked Answered
M

9

44

I have a JWT token like this

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

How can I decode this so that I can get the payload like this

{ "sub": "1234567890", "name": "John Doe", "admin": true }

Melonie answered 1/12, 2016 at 16:26 Comment(1)
Thanks for the question @SiddharthaChikatamalla ... Made my google search time a lot less!Commemorative
M
8

I have got the solution for this.

 static func getJwtBodyString(tokenstr: String) -> NSString {

    var segments = tokenstr.components(separatedBy: ".")
    var base64String = segments[1]
    print("\(base64String)")
    let requiredLength = Int(4 * ceil(Float(base64String.characters.count) / 4.0))
    let nbrPaddings = requiredLength - base64String.characters.count
    if nbrPaddings > 0 {
        let padding = String().padding(toLength: nbrPaddings, withPad: "=", startingAt: 0)
        base64String = base64String.appending(padding)
    }
    base64String = base64String.replacingOccurrences(of: "-", with: "+")
    base64String = base64String.replacingOccurrences(of: "_", with: "/")
    let decodedData = Data(base64Encoded: base64String, options: Data.Base64DecodingOptions(rawValue: UInt(0)))
  //  var decodedString : String = String(decodedData : nsdata as Data, encoding: String.Encoding.utf8)

    let base64Decoded: String = String(data: decodedData! as Data, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue))!
    print("\(base64Decoded)")
    return base64String as NSString
}

This works for me great. Thank you.

Melonie answered 2/12, 2016 at 21:43 Comment(1)
Shouldn't the final return statement return "base64Decoded" instead of "base64String"? (which seems to be somewhat the input string?)Raccoon
Z
90

If you are okay with using a library i would suggest this https://github.com/auth0/JWTDecode.swift

and then import the library import JWTDecode and execute.

let jwt = try decode(jwt: token)

Since you didn't want to include this library i brought out the needed parts to make it work.

func decode(jwtToken jwt: String) -> [String: Any] {
  let segments = jwt.components(separatedBy: ".")
  return decodeJWTPart(segments[1]) ?? [:]
}

func base64UrlDecode(_ value: String) -> Data? {
  var base64 = value
    .replacingOccurrences(of: "-", with: "+")
    .replacingOccurrences(of: "_", with: "/")

  let length = Double(base64.lengthOfBytes(using: String.Encoding.utf8))
  let requiredLength = 4 * ceil(length / 4.0)
  let paddingLength = requiredLength - length
  if paddingLength > 0 {
    let padding = "".padding(toLength: Int(paddingLength), withPad: "=", startingAt: 0)
    base64 = base64 + padding
  }
  return Data(base64Encoded: base64, options: .ignoreUnknownCharacters)
}

func decodeJWTPart(_ value: String) -> [String: Any]? {
  guard let bodyData = base64UrlDecode(value),
    let json = try? JSONSerialization.jsonObject(with: bodyData, options: []), let payload = json as? [String: Any] else {
      return nil
  }

  return payload
}

Call it like this:

decode(jwtToken: TOKEN)
Zalucki answered 1/12, 2016 at 16:29 Comment(3)
Thank you for the response. But I can not use the JWT library, I am implementing it in a framework, I want a simple implementation. In android I use String[] split = JWTEncoded.split("\\."); String body = getJson(split[1]); Log.d("JWT_DECODED", "Body: " + body); private static String getJson(String strEncoded) throws UnsupportedEncodingException{ byte[] decodedBytes = Base64.decode(strEncoded, Base64.URL_SAFE); return new String(decodedBytes, "UTF-8"); }Melonie
any other library with Swift 4 at least?Furuncle
Nice code snippet. From it I created a JWTDecoder struct with static functions and added throws to where it's needed. No need to clog your project with unnecessary frameworks.Endamage
S
50

Iterating on Viktor's code:

  • Use nested functions to keep more modular
  • Utilize exceptions if bad token passed or other errors.
  • Simpler calculation of padding and utilization of padding function.

Hope it is useful:

func decode(jwtToken jwt: String) throws -> [String: Any] {

    enum DecodeErrors: Error {
        case badToken
        case other
    }

    func base64Decode(_ base64: String) throws -> Data {
        let base64 = base64
            .replacingOccurrences(of: "-", with: "+")
            .replacingOccurrences(of: "_", with: "/")
        let padded = base64.padding(toLength: ((base64.count + 3) / 4) * 4, withPad: "=", startingAt: 0)
        guard let decoded = Data(base64Encoded: padded) else {
            throw DecodeErrors.badToken
        }
        return decoded
    }

    func decodeJWTPart(_ value: String) throws -> [String: Any] {
        let bodyData = try base64Decode(value)
        let json = try JSONSerialization.jsonObject(with: bodyData, options: [])
        guard let payload = json as? [String: Any] else {
            throw DecodeErrors.other
        }
        return payload
    }

    let segments = jwt.components(separatedBy: ".")
    return try decodeJWTPart(segments[1])
}
Steel answered 23/5, 2019 at 21:48 Comment(4)
Best answer IMO.Solfeggio
JWT uses "base64url" encoding (not regular "base64" encoding), which is why replacing - and _ is necessary. It's statistically unlikely to see them if you're parsing only the claims section (they don't often occur in plain text) but if you're parsing raw data (like the signature), you'll see them all the time.Suu
OK thanks for feedback, updated code to do that.Steel
Perfect answer since 2019 till today, Thanks @possen.Seasickness
G
10
    func decode(_ token: String) -> [String: AnyObject]? {
    let string = token.components(separatedBy: ".")
    let toDecode = string[1] as String


    var stringtoDecode: String = toDecode.replacingOccurrences(of: "-", with: "+") // 62nd char of encoding
    stringtoDecode = stringtoDecode.replacingOccurrences(of: "_", with: "/") // 63rd char of encoding
    switch (stringtoDecode.utf16.count % 4) {
    case 2: stringtoDecode = "\(stringtoDecode)=="
    case 3: stringtoDecode = "\(stringtoDecode)="
    default: // nothing to do stringtoDecode can stay the same
        print("")
    }
    let dataToDecode = Data(base64Encoded: stringtoDecode, options: [])
    let base64DecodedString = NSString(data: dataToDecode!, encoding: String.Encoding.utf8.rawValue)

    var values: [String: AnyObject]?
    if let string = base64DecodedString {
        if let data = string.data(using: String.Encoding.utf8.rawValue, allowLossyConversion: true) {
            values = try! JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String : AnyObject]
        }
    }
    return values
}
Glidewell answered 28/5, 2018 at 10:39 Comment(2)
Generally, answers are much more helpful if they include an explanation of what the code is intended to do, and why that solves the problem without introducing others.Kike
Didn't want to use external libraries, and this worked like a charm, thanks :)Staminody
M
8

I have got the solution for this.

 static func getJwtBodyString(tokenstr: String) -> NSString {

    var segments = tokenstr.components(separatedBy: ".")
    var base64String = segments[1]
    print("\(base64String)")
    let requiredLength = Int(4 * ceil(Float(base64String.characters.count) / 4.0))
    let nbrPaddings = requiredLength - base64String.characters.count
    if nbrPaddings > 0 {
        let padding = String().padding(toLength: nbrPaddings, withPad: "=", startingAt: 0)
        base64String = base64String.appending(padding)
    }
    base64String = base64String.replacingOccurrences(of: "-", with: "+")
    base64String = base64String.replacingOccurrences(of: "_", with: "/")
    let decodedData = Data(base64Encoded: base64String, options: Data.Base64DecodingOptions(rawValue: UInt(0)))
  //  var decodedString : String = String(decodedData : nsdata as Data, encoding: String.Encoding.utf8)

    let base64Decoded: String = String(data: decodedData! as Data, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue))!
    print("\(base64Decoded)")
    return base64String as NSString
}

This works for me great. Thank you.

Melonie answered 2/12, 2016 at 21:43 Comment(1)
Shouldn't the final return statement return "base64Decoded" instead of "base64String"? (which seems to be somewhat the input string?)Raccoon
U
4

I you want to use a library for that I recommend using something popular from someone big. IBM is making Kitura – Swift backend framework, so the implementation of encoding and decoding JWT for it must be top notch:

Link: https://github.com/IBM-Swift/Swift-JWT

Simple usage for token with expiry date

import SwiftJWT

struct Token: Decodable {
    let jwtString: String

    func abc() {

        do {
            let newJWT = try JWT<MyJWTClaims>(jwtString: jwtString)
            print(newJWT.claims.exp)
        } catch {
            print("OH NOES")
        }


    }
}

struct MyJWTClaims: Claims {
    let exp: Date
}
Urushiol answered 24/7, 2019 at 13:37 Comment(0)
T
4

Objective-C version for oldies:

NSLog(@"credential is %@", credential.identityToken);
NSString * string = [[NSString alloc] initWithData:credential.identityToken encoding:NSUTF8StringEncoding];
NSArray * segments = [string componentsSeparatedByString:@"."];
NSMutableDictionary * JSON = [NSMutableDictionary new];
for (int n = 0; n < segments.count; n++){
    
    NSString * value = segments[n];
    NSString * base64 = [value stringByReplacingOccurrencesOfString:@"-" withString:@"+"];
    base64 = [base64 stringByReplacingOccurrencesOfString:@"_" withString:@"/"];
    NSUInteger length = [base64 lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
    int requiredLength = 4 * ceil((float)length/4.0f);
    int paddingLength = requiredLength - (int)length;
    for (int n = 0; n < paddingLength; n++){
        base64 = [base64 stringByAppendingString:@"="];
    }
    NSData * data = [[NSData alloc] initWithBase64EncodedString:base64 options:0];
    
    NSError * error;
    NSDictionary * local = [NSJSONSerialization JSONObjectWithData:data
                                                           options:NSJSONReadingAllowFragments
                                                             error:&error];
    [JSON addEntriesFromDictionary:local];
}

NSLog(@"JSON is %@", JSON);
Trapezohedron answered 8/11, 2021 at 17:10 Comment(1)
an oldie but a goodie...Estimate
J
3

There's a swift implementation. Add this into your Podfile if you're using CocoaPods or clone the project and use it directly.

JSONWebToken

do {
  // the token that will be decoded
  let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
  let payload = try JWT.decode(token, algorithm: .hs256("secret".data(using: .utf8)!))
  print(payload)
} catch {
  print("Failed to decode JWT: \(error)")
}
Johann answered 1/12, 2016 at 16:29 Comment(0)
N
3

I refactored @Viktor Gardart code

Use like this

let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhY2NvdW56IiwianRpIjoiZTA0NGEyMTAtZjVmZi00Yjc2LWI2MzMtNTk0NjYzMWE0MjRjLWQxYTc3bXlpdGE0YnZnaG4yd2YiLCJleHAiOjE2NDk2NDI3MTF9.FO-AQhZ18qogsSbeTUY78EqhfL9xp9iUG3OlpOdxemE"

let jsonWebToken = JSONWebToken(jsonWebToken: token)
let expirationTime = jsonWebToken?.payload.expirationTime


JSONWebToken.swift

import Foundation

struct JSONWebToken {
    let header: JSONWebTokenHeader
    let payload: JSONWebTokenPayload
    let signature: String
}

extension JSONWebToken {

    init?(jsonWebToken: String) {
        let encodedData = { (string: String) -> Data? in
            var encodedString = string.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
        
            switch (encodedString.utf16.count % 4) {
            case 2:     encodedString = "\(encodedString)=="
            case 3:     encodedString = "\(encodedString)="
            default:    break
            }
        
            return Data(base64Encoded: encodedString)
        }

        let components = jsonWebToken.components(separatedBy: ".")
    
        guard components.count == 3, 
            let headerData = encodedData(components[0] as String), 
            let payloadData = encodedData(components[1] as String) else { return nil }

        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601

        do {
            header    = try decoder.decode(JSONWebTokenHeader.self, from: headerData)
            payload   = try decoder.decode(JSONWebTokenPayload.self, from: payloadData)
            signature = components[2] as String
    
        } catch {
            print(error.localizedDescription)
            return nil
        }
    }
}


JSONWebTokenHeader.swift

import Foundation

struct JSONWebTokenHeader {
    let type: String
    let algorithm: String
}

extension JSONWebTokenHeader: Codable {

    private enum Key: String, CodingKey {
        case type      = "typ"
        case algorithm = "alg"
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: Key.self)
    
        do { try container.encode(type,      forKey: .type) }      catch { throw error }
        do { try container.encode(algorithm, forKey: .algorithm) } catch { throw error }
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: Key.self)

        do { type      = try container.decode(String.self, forKey: .type) }      catch { throw error }
        do { algorithm = try container.decode(String.self, forKey: .algorithm) } catch { throw error }
    }
}


JSONWebTokenPayload.swift

import Foundation

struct JSONWebTokenPayload {
    let issuer: String
    let expirationTime: Double
    let jsonWebTokenID: String
}

extension JSONWebTokenPayload: Codable {

    private enum Key: String, CodingKey {
        case issuer         = "iss"
        case expirationTime = "exp"
        case jsonWebTokenID = "jti"
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: Key.self)
    
        do { try container.encode(issuer,         forKey: .issuer) }         catch { throw error }
        do { try container.encode(expirationTime, forKey: .expirationTime) } catch { throw error }
        do { try container.encode(jsonWebTokenID, forKey: .jsonWebTokenID) } catch { throw error }
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: Key.self)

        do { issuer         = try container.decode(String.self, forKey: .issuer) }         catch { throw error }
        do { expirationTime = try container.decode(Double.self, forKey: .expirationTime) } catch { throw error }
        do { jsonWebTokenID = try container.decode(String.self, forKey: .jsonWebTokenID) } catch { throw error }
    }
}
Nationalize answered 12/4, 2022 at 5:57 Comment(1)
Unfortunately this simply does not work, with most tokens I try (it works fine with the on etest token given).Dariusdarjeeling
B
0

in SwiftUI you can use this:

Working in XCode 14.2(14C18), iOS 15

func decodeJwt(from jwt: String) -> String {
    
    let segments = jwt.components(separatedBy: ".")
    
    var base64String = segments[1]
    
    let requiredLength = Int(4 * ceil(Float(base64String.count) / 4.0))
    let nbrPaddings = requiredLength - base64String.count
    if nbrPaddings > 0 {
        let padding = String().padding(toLength: nbrPaddings, withPad: "=", startingAt: 0)
        base64String = base64String.appending(padding)
    }
    base64String = base64String.replacingOccurrences(of: "-", with: "+")
    base64String = base64String.replacingOccurrences(of: "_", with: "/")
    let decodedData = Data(base64Encoded: base64String, options: Data.Base64DecodingOptions(rawValue: UInt(0)))
    
    let base64Decoded: String = String(data: decodedData! as Data, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue))!
    return base64Decoded
}
Benzoin answered 12/1 at 15:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.